#include "World.h"

#include "Controller.h"
#include "DirectXStuff.h"
#include "Entity.h"
#include "MatrixStack.h"
#include "Network.h"
#include "Player.h"
#include "Serializable.h"
#include "SharedFunctions.h"

World *World::g_instance = NULL;

World::World() : m_currentEntityRef(0)
{

}

// COLLISION
// Ref:: http://www.3dkingdoms.com/weekly/weekly.php?a=3
int inline GetIntersection( float fDst1, float fDst2, D3DXVECTOR3 P1, D3DXVECTOR3 P2, D3DXVECTOR3 &Hit) 
{
	if ( (fDst1 * fDst2) >= 0.0f) return 0;
	if ( fDst1 == fDst2) return 0; 
	Hit = P1 + (P2-P1) * ( -fDst1/(fDst2-fDst1) );
	return 1;
}

int inline InBox( D3DXVECTOR3 Hit, D3DXVECTOR3 B1, D3DXVECTOR3 B2, const int Axis) 
{
	if ( Axis==1 && Hit.z > B1.z && Hit.z < B2.z && Hit.y > B1.y && Hit.y < B2.y) return 1;
	if ( Axis==2 && Hit.z > B1.z && Hit.z < B2.z && Hit.x > B1.x && Hit.x < B2.x) return 1;
	if ( Axis==3 && Hit.x > B1.x && Hit.x < B2.x && Hit.y > B1.y && Hit.y < B2.y) return 1;
	return 0;
}

bool CheckLineBox( D3DXVECTOR3 B1, D3DXVECTOR3 B2, D3DXVECTOR3 L1, D3DXVECTOR3 L2, D3DXVECTOR3 &Hit)
{
	if (L2.x < B1.x && L1.x < B1.x) return false;
	if (L2.x > B2.x && L1.x > B2.x) return false;
	if (L2.y < B1.y && L1.y < B1.y) return false;
	if (L2.y > B2.y && L1.y > B2.y) return false;
	if (L2.z < B1.z && L1.z < B1.z) return false;
	if (L2.z > B2.z && L1.z > B2.z) return false;
	if (L1.x > B1.x && L1.x < B2.x &&
	    L1.y > B1.y && L1.y < B2.y &&
	    L1.z > B1.z && L1.z < B2.z) 
	    {Hit = L1; 
	    return true;}
	if ( (GetIntersection( L1.x-B1.x, L2.x-B1.x, L1, L2, Hit) && InBox( Hit, B1, B2, 1 ))
	  || (GetIntersection( L1.y-B1.y, L2.y-B1.y, L1, L2, Hit) && InBox( Hit, B1, B2, 2 )) 
	  || (GetIntersection( L1.z-B1.z, L2.z-B1.z, L1, L2, Hit) && InBox( Hit, B1, B2, 3 )) 
	  || (GetIntersection( L1.x-B2.x, L2.x-B2.x, L1, L2, Hit) && InBox( Hit, B1, B2, 1 )) 
	  || (GetIntersection( L1.y-B2.y, L2.y-B2.y, L1, L2, Hit) && InBox( Hit, B1, B2, 2 )) 
	  || (GetIntersection( L1.z-B2.z, L2.z-B2.z, L1, L2, Hit) && InBox( Hit, B1, B2, 3 )))
		return true;
	
	return false;
}

//Make Length a length of an insanely large number so that scaling will result in a long enough vector for any position? 
bool World::CalculateShot(D3DXVECTOR3 _Direction, float _Length, CubeVolume* _Cube)
{
	//Need to calculate the directional vector (using Chris' function)
	D3DXVECTOR3 CollisionPoint;
	if(CheckLineBox(_Cube->GetMin(), _Cube->GetMax(), _Direction, _Direction + D3DXVECTOR3(100.0f, 0.0f, 100.0f)/* * _Length*/, CollisionPoint))
	{
		return true;
	}
	return false;
}

short World::CalculateShot(short Reference)
{
	Entity* TempEntity = m_entities[Reference];
	CubeVolume* TempCube = TempEntity->GetCube();
	//Need to calculate the directional vector (using Chris' function) 
	//_Matrix.m[3][0]
	const int Length = 500;
	D3DXVECTOR3 CollisionPoint;
	D3DXVECTOR3 Position(TempEntity->m_data.data.Matrix.m[3][0], TempEntity->m_data.data.Matrix.m[3][1], TempEntity->m_data.data.Matrix.m[3][2]);
	D3DXVECTOR3 Direction(VectorMatrixMultiply(TempEntity->m_data.data.Matrix, D3DXVECTOR3(0.0f, 0.0f, 1.0f)));
	//D3DXVECTOR3 NormalizedDestination;
	//D3DXVec3Normalize(&NormalizedDestination, &DirectionStart);

	D3DXVECTOR3 DestinationEnd = D3DXVECTOR3(Position + Direction * Length);

	//Create list of other entities in the world
	//Sort them by distance (From the position vector of the reference taken in)
	//Then test their physics box against the line taken in
	//If is hits (true), return the reference of the first entity hit
	//else return -1

	//Check the shot against all entities
	for( std::map<short, Entity*>::iterator i = m_entities.begin(); i != m_entities.end(); ++i)
	{
		if(CheckLineBox(i->second->GetCube()->GetMin(), i->second->GetCube()->GetMax(), Position, DestinationEnd, CollisionPoint))
		{
			return i->first;
		}
	}
	return -1;

	/*
	Entity* TempEntity = m_entities[Reference];
	CubeVolume* TempCube = TempEntity->GetCube();
	//Need to calculate the directional vector (using Chris' function) 
	//_Matrix.m[3][0]
	const int Length = 50;
	D3DXVECTOR3 CollisionPoint;
	D3DXVECTOR3 Position(TempEntity->GetMatrix().m[3][0], TempEntity->GetMatrix().m[3][1], TempEntity->GetMatrix().m[3][2]);
	D3DXVECTOR3 DirectionStart(VectorMatrixMultiply(TempEntity->GetMatrix(), D3DXVECTOR3(0.0f, 0.0f, 01.0f)));
	D3DXVECTOR3 NormalizedDestination;
	D3DXVec3Normalize(&NormalizedDestination, &DirectionStart);

	D3DXVECTOR3 DestinationEnd = D3DXVECTOR3(DirectionStart + NormalizedDestination * Length);


	if(CheckLineBox(TempCube->GetMin(), TempCube->GetMax(), DirectionStart, DestinationEnd, CollisionPoint))
	{
		return true;
	}
	return false;
	*/
}

void World::Update(float _DeltaT)
{
	//Update every entity to the latest tick
	for( std::map<short, Entity*>::iterator i = m_entities.begin(); i != m_entities.end(); ++i)
	{
		i->second->ToTick(Network::Get()->GetCurrentTick());
	}

	//Update all entities
	for( std::map<short, Entity*>::iterator i = m_entities.begin(); i != m_entities.end(); ++i)
	{
		i->second->Update(_DeltaT);
	}

	//Update all controllers
	for( unsigned int i = 0; i < m_controllers.size(); ++i)
	{
		m_controllers[i]->Update(_DeltaT);
	}

	//Check the network for any messages
	while( Network::Get()->GetEventCount() > 0 )
	{
		GameMessage message = Network::Get()->GetNextEvent();

		//Check what type of message has been received
		switch( message.messageType )
		{
		case NM_SHOOT:
			//If this is the server, calculate shot
			if( Network::Get()->IsServer() )
			{
				EntityMessage ShotMessage = *(EntityMessage*)&message.data;
				ShotMessage.entityReference = ntohs(ShotMessage.entityReference);
				short Result = CalculateShot(ShotMessage.entityReference);
				if(Result != -1)
				{
					//Somebody has been shot
					std::cout << "Client " << Result << " has been hit!" << std::endl;

					//You Hit an anemy
					//Tell them they have been hit
					//Player* TempPlayer = (Player*)m_entities[Result];
					//TempPlayer->TakeDamage();
				}
			}
			break;

		case NM_NEW_ENTITY:
			//Create a player at the specified position
			{
				NewEntityMessage eMessage = *(NewEntityMessage*)&message.data;
				eMessage.entityReference = ntohs(eMessage.entityReference);
				Mesh* tempMesh = new Mesh(g_pd3dDevice, "Cube.x");
				AddEntity( eMessage.entityReference, new Player(eMessage.entityReference, tempMesh, false) );
			}
			break;

		default:
			Log("	WARNING: World has received an unhandled message type");
			break;
		}
	}

	//Send data for all appliccable entities
	if( Network::Get()->IsServer() )
	{
		SendServer();
	} else {
		SendClient();
	}
}

void World::RenderObjects()
{
	//Render all entities
	for( std::map<short, Entity*>::iterator i = m_entities.begin(); i != m_entities.end(); ++i)
	{
		i->second->Render();
		#ifdef _DEBUG
		RenderPhysics(i->second);
		#endif
	}
}

void World::RenderPhysics(Entity *entity)
{
	//Create a line for this entity
	LPD3DXLINE myLine = NULL;
	D3DXCreateLine(g_pd3dDevice, &myLine);
	myLine->SetWidth(2);
	//myLine->SetGLLines(TRUE);
	
	//----------------------------------------------------------------------------------------
	//Render Physics
	myLine->Begin();
	std::vector<D3DXVECTOR3> Vertices3D;//Vector to hold the Verticies (3D vector)

	if(entity->GetCube() != NULL)
	{
		//Bottom face
		Vertices3D.push_back(D3DXVECTOR3(entity->GetCube()->left,  entity->GetCube()->bottom, entity->GetCube()->back));
		Vertices3D.push_back(D3DXVECTOR3(entity->GetCube()->left,  entity->GetCube()->bottom, entity->GetCube()->front));
		Vertices3D.push_back(D3DXVECTOR3(entity->GetCube()->right, entity->GetCube()->bottom, entity->GetCube()->front));
		Vertices3D.push_back(D3DXVECTOR3(entity->GetCube()->right, entity->GetCube()->bottom, entity->GetCube()->back));
		Vertices3D.push_back(D3DXVECTOR3(entity->GetCube()->left,  entity->GetCube()->bottom, entity->GetCube()->back));

		//Back Face
		Vertices3D.push_back(D3DXVECTOR3(entity->GetCube()->left,  entity->GetCube()->top,    entity->GetCube()->back));
		Vertices3D.push_back(D3DXVECTOR3(entity->GetCube()->right, entity->GetCube()->top,    entity->GetCube()->back));
		Vertices3D.push_back(D3DXVECTOR3(entity->GetCube()->right, entity->GetCube()->bottom, entity->GetCube()->back));

		//Draw the lines relevent to the camera' Matracies
		myLine->DrawTransform(&Vertices3D[0], Vertices3D.size(), &MatrixStack::ReturnInstance()->ReturnTop(), D3DCOLOR_ARGB( 255, 255, 0, 0 ));
		
		//Clear the buffer of verticies for the sprite
		Vertices3D.clear();
	}

	myLine->End();
	myLine->Release();
}

void World::DrawHUD()
{
	//Create Drawing Rectangle
	RECT fontPos;
	fontPos.left = 0; fontPos.top = 0;
	fontPos.right = 500; fontPos.bottom = 500;
	char buffer[30];//Buffer for the text

	sprintf_s(buffer, "Score %i :", 7 /*World::Get().Controler->m_Score*/);
	g_font->DrawTextA(NULL, buffer, -1, &fontPos, DT_LEFT | DT_WORDBREAK, 0xffffffff);
}

void World::SendServer()
{
	//Send all data for all entities
	for(std::map<short, Entity*>::iterator i = m_entities.begin(); i != m_entities.end(); ++i)
	{
		char data[MAX_MESSAGE_SIZE];
		data << *i->second;
		Network::Get()->SendEntity(data, i->second->GetSize(), i->first);
	}
}

void World::SendClient()
{
	//Only send the entity data for our entity
	if(m_entities.size() > 0)
	{
		char data[MAX_MESSAGE_SIZE];
		data << *m_entities[Network::Get()->GetThisClientReference()];
		Network::Get()->SendEntity(data, m_entities[Network::Get()->GetThisClientReference()]->GetSize(), Network::Get()->GetThisClientReference() );
	}
}

void World::CalculateShot(GameMessage _Msg)
{
	//calculate to see if the shot (trace) intersects any entities at their nearest possible tick
		//if it does tell the (server's) network to send a return msg to all clients, telling them that they hit
	
	_Msg.messageType = NM_ERROR; //Remove this when writing this function, just here to keep the warning from preventing compliation
}

short World::AddEntityAutoRef(Entity *e)
{
	//Increment the current entity reference
	++m_currentEntityRef;
	AddEntity( (m_currentEntityRef - 1), e);
	return (m_currentEntityRef - 1);
}

void World::AddEntity(short reference, Entity *e)
{
	m_entities.insert( std::pair<short, Entity *>(reference, e) );
}

void World::AddController(Controller *c)
{
	m_controllers.push_back(c);
}